﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.Guids;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Uow;
using Volo.Abp.Users;

namespace Mozhi.Abp.OperationLog
{
    public class DefaultOperationLogger : IOperationLogger
    {
        private ICurrentUser _currentUser;
        private IGuidGenerator _guidGenerator;
        private ICurrentTenant _currentTenant;
        private readonly IDistributedEventBus _distributedEventBus;

        public DefaultOperationLogger(IGuidGenerator guidGenerator, ICurrentUser currentUser , ICurrentTenant currentTenant , IDistributedEventBus distributedEventBus)
        {
            _guidGenerator = guidGenerator;
            _currentUser = currentUser;
            _currentTenant = currentTenant;
            _distributedEventBus = distributedEventBus;
        }

        public virtual async Task LogAsync(string templateCode, OperationType operationType, object oldObject, object newObject)
        {
            var logEvent = GetRecordEvent(templateCode, operationType, oldObject, newObject);

            await _distributedEventBus.PublishAsync(logEvent);
        }

        public OperationLogRecordEvent GetRecordEvent(string templateCode, OperationType operationType, object oldObject, object newObject)
        {
            var properties = oldObject.GetType().GetProperties();

            var idProperty = properties.FirstOrDefault(x => x.Name == "Id");

            var idPropertyValue = idProperty.GetValue(oldObject);

            Guid? id = idProperty == null || idPropertyValue == null ? null : (Guid?)Guid.Parse(idPropertyValue.ToString());

            var nameProperty = properties.FirstOrDefault(x => x.Name == "Name");

            var namePropertypeValue = nameProperty == null ? null : nameProperty.GetValue(oldObject);

            var codeProperty = properties.FirstOrDefault(x => x.Name == "Code");

            var codePropertyValue = codeProperty == null ? null : codeProperty.GetValue(oldObject);

            var logEvent = new OperationLogRecordEvent(_guidGenerator.Create(), _currentTenant.Id, _currentUser?.Id, _currentUser?.UserName, operationType, templateCode, null, id ?? default, $"{codePropertyValue} {namePropertypeValue}", "");

            if (operationType == OperationType.Update)
                SetLogItems(logEvent, oldObject, newObject, properties.ToList());

            return logEvent;
        }

        #region
        protected virtual void SetLogItems(OperationLogRecordEvent log, object oldObject, object newObject, List<PropertyInfo> properties)
        {
            if (properties == null || properties.Count == 0)
                return;

            var items = new List<OperationLogRecordEventItem>();

            foreach (var property in properties)
            {
                if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(IList<>))
                {
                    //列表类型
                    var oldValues = property.GetValue(oldObject);
                    var newValues = property.GetValue(newObject);

                    if (oldValues == null && newValues == null)
                        continue;

                    var oldValuesType = oldValues.GetType();
                    var oldValuesCount = Convert.ToInt32(oldValuesType.GetProperty("Count").GetValue(oldValues, null));

                    var newValuesType = newValues.GetType();
                    var newValuesCount = Convert.ToInt32(newValuesType.GetProperty("Count").GetValue(newValues, null));

                    var defferentCount = oldValuesCount - newValuesCount;

                    if (defferentCount != 0)
                    {
                        var fieldCaption = "分录";

                        fieldCaption += defferentCount > 0 ? "移除" : "新增";

                        var logItem = new OperationLogRecordEventItem(_guidGenerator.Create(), _currentTenant.Id, log.Id, property.Name, fieldCaption,
                            oldValuesCount, oldValuesCount.ToString(), newValuesCount, newValuesCount.ToString(), null);

                        items.Add(logItem);
                    }
                    else
                    {
                        var itemProperties = oldValuesType.GetProperties();

                        for (var i = 0; i < oldValuesCount; i++)
                        {
                            var oldItem = oldValuesType.GetProperty("Item").GetValue(oldValues, new object[] { i });

                            var itemId = oldItem.GetType().GetProperty("Id").GetValue(oldItem, null);

                            var matchNewItem = GetMatchNewItem(itemId, newValuesType, newValues, newValuesCount);

                            //找不到匹配的项目
                            if (matchNewItem == null)
                                continue;

                            foreach (var itemProperty in itemProperties)
                            {
                                var logItem = GetOperationLogItemAsync(log.Id, oldItem, matchNewItem, itemProperty);

                                if (logItem != null)
                                    items.Add(logItem);
                            }
                        }
                    }
                }
                else
                {
                    var logItem = GetOperationLogItemAsync(log.Id, oldObject, newObject, property);

                    if (logItem != null)
                        items.Add(logItem);
                }
            }

            log.ChangeItems(items);
        }

        protected virtual object GetMatchNewItem(object id, Type newValuesType, object newValues, int newValuesCount)
        {
            object result = null;

            for (var i = 0; i < newValuesCount; i++)
            {
                var item = newValuesType.GetProperty("Item").GetValue(newValues, new object[] { i });

                var itemId = item.GetType().GetProperty("Id").GetValue(item, null);

                if (itemId == id)
                {
                    result = item;
                    break;
                }
            }

            return result;
        }

        protected virtual OperationLogRecordEventItem GetOperationLogItemAsync(Guid logId, object oldObject, object newObject, PropertyInfo property)
        {
            OperationLogRecordEventItem result = null;

            var oldValue = property.GetValue(oldObject);
            var newValue = property.GetValue(newObject);

            if ((oldValue == null && newValue == null) || (oldValue != null && oldValue.Equals(newValue)))
                return result;

            result = new OperationLogRecordEventItem(_guidGenerator.Create(), _currentTenant.Id , logId, property.Name, null, oldValue, null, newValue, null, null);

            return result;
        }
        #endregion
    }
}
